using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.PeerResolvers;
using System.ServiceModel.Dispatcher;

namespace C4F.DevKit.P2PPictureShare.PictureShareWpf
{
    // WCF service contract that defines how messages are sent/received
    // to/from the Peer-to-Peer network.
    [ServiceContract]
    interface IPictureSharer
    {
        // Note: P2P communication requries the one way operation contract.
        [OperationContract(IsOneWay = true)]
        void SharePicture(PictureShareMessage message);
    }

    // WCF message contract that defines the message structure used.
    [MessageContract]
    class PictureShareMessage
    {
        // Picture filename.
        [MessageHeader]
        String _fileName;

        // Picture stream data.
        [MessageBodyMember]
        Stream _pictureStream;

        public String PictureName
        {
            get { return this._fileName; }
        }

        public Stream PictureStream
        {
            get { return this._pictureStream; }
        }

        public PictureShareMessage()
        { }

        public PictureShareMessage(String fileName, Stream pictureStream)
        {
            if (fileName == null)
            { throw new ArgumentNullException("fileName"); }
            if (pictureStream == null)
            { throw new ArgumentNullException("pictureStream"); }

            this._fileName = fileName;
            this._pictureStream = pictureStream;
        }
    }

    // Defines the peer channel behavior and hosts the service endpoint
    // so that picture sharing messages can be sent and received.
    // Note: we must implement the IPictureSharer service contract.
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    class PictureSharePeerChannel : IPictureSharer
    {
        // Define full implementation requirements for the peer channel
        // that we are creating.
        interface IPictureViewerClient : IPictureSharer, IClientChannel { }

        // Will generate the picture sharing peer channel.
        ChannelFactory<IPictureViewerClient> _channelFactory = null;

        // The generated picture sharing peer channel will be the type
        // defined by the service contract, IPictureSharer.
        IPictureSharer _channel = null;

        // P2P address (e.g. net.p2p://PictureShare)
        EndpointAddress _address = null;

        // We host an instance of the WCF service for the picture sharing
        // P2P application.
        ServiceHost _host = null;

        // Our picture cache manager.
        PictureShareCacheManager _cacheMgr = null;


        public PictureSharePeerChannel()
        {
            // Setup picture cache manager.
            this._cacheMgr =
                new PictureShareCacheManager(SettingsWrapper.CacheFolder);

            // Setup peer channel connection.
            Connect();
        }

        #region IPictureSharer Members

        // Implementation of service contract.
        // Note: This method is called when a message is RECEIVED
        // from the P2P network. See SendPicture method for example
        // of how a message is SENT.
        public void SharePicture(PictureShareMessage message)
        {
            // Use our cache manager to save the message contents.
            this._cacheMgr.Save(message.PictureStream, message.PictureName);
        }

        #endregion


        public void Connect()
        {
            // Construct the full P2P network mesh address.
            // Note: all P2P addresses start with "net.p2p://" for the protocol.
            Uri meshAddress = new Uri("net.p2p://" + SettingsWrapper.P2PAddress);

            // Define the binding used for P2P.
            NetPeerTcpBinding binding = new NetPeerTcpBinding();
            // Using Peer Name Resolution Protocol (PNRP) to find other instances
            // of the picture sharing application.
            binding.Resolver.Mode = PeerResolverMode.Pnrp;
            // Using plain text password for simplicity.
            binding.Security.Transport.CredentialType = PeerTransportCredentialType.Password;
            // Set the maximum size of a message in bytes.
            binding.MaxReceivedMessageSize = 1000000L;
            binding.ReaderQuotas.MaxDepth = int.MaxValue;
            binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
            binding.ReaderQuotas.MaxArrayLength = int.MaxValue;
            binding.ReaderQuotas.MaxBytesPerRead = int.MaxValue;
            binding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;
            binding.Security.Mode = SecurityMode.None;

            // Setup and define a WCF service host.
            this._host = new ServiceHost(this);
            this._host.AddServiceEndpoint(typeof(IPictureSharer), binding, meshAddress);
            this._host.Credentials.Peer.MeshPassword = SettingsWrapper.P2PPassword;
            this._host.Open();

            // Create the P2P channel using a channel factory.
            this._address = new EndpointAddress(meshAddress);
            this._channelFactory = new ChannelFactory<IPictureViewerClient>(binding, this._address);
            this._channelFactory.Credentials.Peer.MeshPassword = SettingsWrapper.P2PPassword;
            this._channel = this._channelFactory.CreateChannel();
        }
       
        public void SendPicture(string fileName)
        {
            // Perform some checks on the provided filename.
            if (fileName == null)
            { throw new ArgumentNullException("fileName"); }
            if (!Path.IsPathRooted(fileName))
            { throw new ArgumentException("File does not include full path.", "fileName"); }
            if (!File.Exists(fileName))
            { throw new ArgumentException("File not found.", "fileName"); }

            // Stream that will be used to save picture information into.
            MemoryStream picStream = new MemoryStream();

            try
            {
                // Load the picture filename into an Image object.
                using (Image origPic = new Bitmap(fileName))
                {
                    // MaxSize represents the largest pixel count that is allowed
                    // in any direction (width or height).
                    uint MaxSize = SettingsWrapper.MaxPictureSize;

                    // Image will be shrunk if needed, maintaining aspect ratio.
                    Image finalPic = ShrinkImage(origPic);

                    // Save picture bits to the memory stream.
                    finalPic.Save(picStream, ImageFormat.Jpeg);
                    finalPic.Dispose();
                }
            }
            // If the file is not a valid image, we ignore it.
            catch (ArgumentException)
            {
                return;
            }

            // Reset the stream position to the beginning so that all
            // P2P nodes that receive the message can simply read to the end of the
            // stream to get the picture data.
            picStream.Position = 0;

            // Use our P2P channel to broadcast the message to all listening nodes.
            this._channel.SharePicture(new PictureShareMessage(
                Path.GetFileNameWithoutExtension(fileName), picStream));
        }

        // Shrinks a given image so that the largest side (width or height)
        // is no larger than a configured maximum pixel value. Images that already
        // fit within this requirement are not altered.
        private Image ShrinkImage(Image img)
        {
            double shrinkPercentage = 0;
            Image finalPic = img;
            uint MaxSize = SettingsWrapper.MaxPictureSize;

            if (img.Width >= img.Height && img.Width > MaxSize)
            {
                shrinkPercentage = (double)MaxSize / (double)img.Width;
            }
            else if (img.Height >= img.Width && img.Height > MaxSize)
            {
                shrinkPercentage = (double)MaxSize / (double)img.Height;
            }

            if (shrinkPercentage != 0)
            {
                int newWidth = (int)(img.Width * shrinkPercentage);
                int newHeight = (int)(img.Height * shrinkPercentage);
                finalPic = new Bitmap(img, newWidth, newHeight);
            }

            return finalPic;
        }

        public void Close()
        {
            this._host.Close();
        }
    }
}
